// 并发下载
async function runParallel(maxConcurrency = 3, source = [], iteratorFn) {
  const ret = []
  const executing = []
  for (const item of source) {
    const p = Promise.resolve().then(() => iteratorFn(item, source))
    ret.push(p)

    if (maxConcurrency <= source.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1))
      executing.push(e)
      if (executing.length >= maxConcurrency) {
        await Promise.race(executing)
      }
    }
  }
  return Promise.all(ret)
}

const noop = () => {}

// 并发任务
const defaults = {
  data: [], // 数据队列
  fn: () => {}, // 执行函数
  concurrentNum: 3, // 并发数量
  errorLimit: 20, // 错误停止数量
  onprogress: noop, // 任务进行进度
  onsuccess: noop, // 任务完成
  onerror: noop, // 任务失败
}

class Download {
  constructor(options) {
    Object.assign(this, defaults, options)
    this.errors = []
    this.total = this.data.length // 任务总数
    this.executed = 0
    this.start()
  }

  // 开始任务
  start() {
    runParallel(this.concurrentNum, this.data, this.startItem)
      .then(this.onsuccess)
      .catch(() => this.onerror(this.errors))
  }

  // 取消任务
  cancel = () => {
    this.errorLimit = 0
    this.errors.push(new Error('cancel'))
  }

  // 单个任务执行
  startItem = async (item, data) => {
    if (this.errors.length >= this.errorLimit) {
      return Promise.reject(this.errors[this.errors.length - 1])
    }

    // 错误数量过多
    this.executed += 1
    const percent = ((this.executed / this.total) * 100).toFixed(2)
    try {
      const res = await this.fn(item)
      if (this.errors.length >= this.errorLimit) return
      this.onprogress(percent, this.executed, this.total, res, null)
      return res
    } catch (e) {
      console.log('startItem', e)
      this.errors.push(e)
      if (this.errors.length >= this.errorLimit) {
        return Promise.reject(this.errors[this.errors.length - 1])
      } else {
        this.onprogress(percent, this.executed, this.total, null, e)
      }
    }
  }
}

module.exports = Download
